package com.bdilab.iot.aop;


import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.bdilab.iot.annotation.LogOperation;
import com.bdilab.iot.annotation.LogTag;
import com.bdilab.iot.consts.Constants;
import com.bdilab.iot.enums.OperationType;
import com.bdilab.iot.model.SysLog;
import com.bdilab.iot.model.User;
import com.bdilab.iot.service.SysLogService;
import com.bdilab.iot.utils.IpUtils;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.TextNode;
import com.google.common.collect.Maps;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import org.springframework.validation.BindingResult;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.*;

/**
 * @author WangNing
 * @version 1.0
 * @date 2020/8/1 14:57
 * 日志处理
 */
@Slf4j
@Aspect
@Component
public class LogAspect {

    @Value("${syslog.select:false}")
    private boolean logSelect;

    @Autowired
    SysLogService sysLogService;

    @Pointcut("@annotation(com.bdilab.iot.annotation.LogOperation)")
    public void logPoint() {}

    @AfterReturning(value = "logPoint()", returning = "result")
    public void afterReturn(JoinPoint jp,  Object result) {
        if(needLog(jp)) {
            SysLog sysLog = buildLog(jp, result, null);
            sysLogService.saveLog(sysLog);
        }
    }

    @AfterThrowing(value = "logPoint()", throwing = "ex")
    public void afterReturn(JoinPoint jp,  Throwable ex) {
        if(needLog(jp)){
            SysLog sysLog = buildLog(jp, null, ex);
            sysLogService.saveLog(sysLog);
        }

    }

    private SysLog buildLog(JoinPoint joinPoint, Object result, Throwable ex){

        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        assert attributes != null;
        HttpServletRequest request = attributes.getRequest();

        MethodSignature signature = (MethodSignature) joinPoint.getSignature();

        LogOperation logOperation = signature.getMethod().getAnnotation(LogOperation.class);

        LogTag logTag = joinPoint.getTarget().getClass().getAnnotation(LogTag.class);

        String ip = IpUtils.getRemoteAddr(request);

        User user = (User) request.getAttribute(Constants.CURRENT_USER);

        if(user == null){
            user = new User();
            user.setId(-1L);
            user.setUsername("匿名用户");
        }

        String userAgent = request.getHeader("User-Agent");
        userAgent = StringUtils.isEmpty(userAgent)?"未知客户端":userAgent;

        String input = handleInput(joinPoint.getArgs(), Arrays.asList(logOperation.sensitiveParams()));

        String output = handleOutput(result, logOperation.ignoreOutput());

        String ex_msg = handleException(ex);

        String operationName = String.format("%s:%s", logTag == null?"unknown":logTag.value(), logOperation.operationName());

        return SysLog.builder().userId(user.getId())
                .username(user.getUsername())
                .ip(ip).userAgent(userAgent)
                .input(input).output(output)
                .exMsg(ex_msg).operationName(operationName)
                .operationType(logOperation.operationType())
                .build();
    }

    /**
     * 处理输入参数
     *
     * @param args 入参
     * @param sensitiveParams 敏感参数关键字
     * @return 特殊处理都的入参
     */
    private String handleInput(Object[] args, List<String> sensitiveParams)  {

        Map<String, Object> argMap = Maps.newTreeMap();

        ObjectMapper om = new ObjectMapper();
        if (!ObjectUtils.isEmpty(args)) {
            for (int i = 0; i < args.length; i++) {
                if(filterArgs(args[i]))
                    continue;
                if (args[i] != null && !ObjectUtils.isEmpty(sensitiveParams)) {
                    try {
                        JsonNode  root = om.readTree(om.writeValueAsString(args[i]));
                        handleSensitiveParams(root, sensitiveParams);
                        argMap.put("arg" + (i + 1), root);
                    } catch (IOException e) {
                        argMap.put("arg" + (i + 1), "[exception]");
                    }
                } else {
                    argMap.put("arg" + (i + 1), args[i]);
                }
            }
        }
        try{
            return om.writeValueAsString(argMap);
        }catch (JsonProcessingException e){
            return "{parse args exception}";
        }

    }

    /**
     * 处理敏感参数
     *
     * @param root jackson节点
     * @param params 敏感参数名列表
     */
    private void handleSensitiveParams(JsonNode  root, List<String> params) {


        if (root.isObject()) {

            Iterator<Map.Entry<String, JsonNode>> rootIt = root.fields();
            while (rootIt.hasNext()) {
                Map.Entry<String, JsonNode> node = rootIt.next();
                if (params.contains(node.getKey())) {
                    node.setValue(new TextNode("[hidden]"));
                } else {
                    JsonNode tmpNode = node.getValue();
                    if (tmpNode.isObject()) {
                        handleSensitiveParams(tmpNode, params);
                    } else if (tmpNode.isArray()) {
                        for (JsonNode jsonNode : tmpNode) {
                            handleSensitiveParams(jsonNode, params);
                        }
                    }
                }
            }
        } else if (root.isArray()) {

            for (JsonNode jsonNode : root) {
                handleSensitiveParams(jsonNode, params);
            }

        }
    }

    /**
     * 过滤常用的Controller输入参数
     * @param args
     * @return
     */
    private boolean filterArgs(Object args){
        if(args == null)
            return true;
        return args instanceof User || args instanceof MultipartFile || args instanceof HttpServletRequest ||
                args instanceof ServletResponse || args instanceof BindingResult;
    }

    /**
     * 处理异常信息
     *
     * @param ex 异常对象
     * @return 处理后的异常信息
     */
    private String handleException(Throwable ex) {
        return ex == null ? null : ex.toString();
    }

    /**
     * 处理输出结果
     *
     * @param result 源输出结果
     * @param ignore 是否忽略结果
     * @return 处理后的输出结果
     */
    private String handleOutput(Object result, boolean ignore) {

        return (ignore || result == null) ? null : JSON.toJSONString(result);
    }

    /**
     * 判断是否需要记录查询日志
     */
    private boolean needLog(JoinPoint joinPoint){
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        LogOperation logOperation = signature.getMethod().getAnnotation(LogOperation.class);
        if(logOperation == null)
            return false;
        if(logOperation.operationType() == OperationType.SELECT){
            return logSelect;
        }
        return true;
    }


}
